package com.ocm.core.domain;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

import com.ocm.core.exception.ExceptionCode;
import com.ocm.core.exception.OCMRuntimeException;
import com.ocm.core.exception.TypeCodeException;
import com.ocm.core.util.DateUtil;
import com.ocm.core.util.OCMUtil;

@Entity
@Table(name = "CLASSDEFINITION")
@NamedQueries( {
      @NamedQuery(name = ClassDefinition.FIND_ALL, query = "SELECT classDef FROM ClassDefinition classDef"),
      @NamedQuery(name = ClassDefinition.FIND_NAME, query = "SELECT classDef FROM ClassDefinition classDef WHERE LOWER(classDef.name)=LOWER(:name) and classDef.storage.id=:id"),
      @NamedQuery(name = ClassDefinition.FIND_UUID, query = "SELECT classDef FROM ClassDefinition classDef WHERE LOWER(classDef.classDefUID)=LOWER(:uuid) and classDef.storage.id=:id"),
      @NamedQuery(name = ClassDefinition.FIND_PARENT_ID, query = "SELECT classDef FROM ClassDefinition classDef WHERE classDef.parentClassDefUID.id=:parentId"),
      @NamedQuery(name = ClassDefinition.FIND_PARENT_ID_AND_TYPE, query = "SELECT classDef FROM ClassDefinition classDef WHERE classDef.parentClassDefUID.id=:parentId and classDef.classDefinitionType in (:types)"),
      @NamedQuery(name = ClassDefinition.FIND_PARENT_ID_NULL, query = "SELECT classDef FROM ClassDefinition classDef WHERE classDef.parentClassDefUID is null and classDef.storage.id=:id")})
public class ClassDefinition implements Persistable {

   public static final String FIND_ALL = "ClassDefinition.FIND_ALL";

   public static final String FIND_NAME = "ClassDefinition.FIND_NAME";

   public static final String FIND_UUID = "ClassDefinition.FIND_UUID";

   public static final String FIND_PARENT_ID = "ClassDefinition.FIND_PARENT_ID";

   public static final String FIND_PARENT_ID_AND_TYPE = "ClassDefinition.FIND_PARENT_ID_AND_TYPE";

   public static final String FIND_PARENT_ID_NULL = "ClassDefinition.FIND_PARENT_ID_NULL";

   public static final String NAME_PARA = "name";

   public static final String ID_PARA = "id";

   public static final String UUID_PARA = "uuid";

   public static final String PARENT_ID_PARA = "parentId";

   public static final String TYPES_PARA = "types";

   @Id
   @GeneratedValue(strategy = GenerationType.TABLE, generator = "GEN_CLASSDEF")
   @TableGenerator(name = "GEN_CLASSDEF", table = "SEQUENCE_LIST", allocationSize = 1, initialValue = 1, pkColumnName = "NAME", valueColumnName = "NEXT_SEQUENCE", pkColumnValue = "CLASSDEF")
   @Column(name = "CLASSDEF_ID")
   private Long id;

   @Column(name = "CLASSDEF_NAME", nullable = false)
   private String name;

   @Column(name = "DISPLAY_NAME", nullable = false)
   private String displayName;

   @Column(name = "CLASSDEF_TYPE")
   @Enumerated(EnumType.ORDINAL)
   private ClassDefinitionType classDefinitionType;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "CLASSDEF_PROPERTYDEF", joinColumns = { @JoinColumn(name = "CLASSDEF_ID") }, inverseJoinColumns = { @JoinColumn(name = "PROPERTYDEF_ID") })
   private Set<PropertyDefinition> propertyDefs;

   @OneToOne(cascade=CascadeType.ALL)
   private Security security;

   @Column(name = "CLASSDEF_UID", unique = true, nullable = false)
   private String classDefUID;

   @ManyToOne
   @JoinColumn(name = "PARENT_CLASSDEF_ID")
   private ClassDefinition parentClassDefUID;

   @ManyToOne
   @JoinColumn(name = "STORAGE_ID")
   private Storage storage;

   @Column(name = "REAL_PATH")
   private String realPath;

   @Column(name = "DESCRIPTION")
   private String description;

   @Column(name = "CREATED_USER")
   private String createdUser;

   @Column(name = "CREATED_DATE")
   private Date createdDated;

   @Column(name = "MODIFYED_USER")
   private String modifyedUser;

   @Column(name = "MODIFYED_DATE")
   private Date modifyedDated;

   public ClassDefinition() {
      propertyDefs = new HashSet<PropertyDefinition>();
   }

   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getDisplayName() {
      return displayName;
   }

   public void setDisplayName(String displayName) {
      this.displayName = displayName;
   }

   public ClassDefinitionType getClassDefinitionType() {
      return classDefinitionType;
   }

   public void setClassDefinitionType(ClassDefinitionType classDefinitionType) {
      this.classDefinitionType = classDefinitionType;
   }

   public List<PropertyDefinition> getPropertyDefs() {
      return new ArrayList<PropertyDefinition>(this.propertyDefs);
   }

   public void setPropertyDefs(List<PropertyDefinition> propertyDefs) {
      this.propertyDefs.clear();
      this.propertyDefs.addAll(propertyDefs);
   }

   public String getClassDefUID() {
      return classDefUID;
   }

   public void setClassDefUID(String classDefUID) {
      this.classDefUID = classDefUID;
   }

   public ClassDefinition getParentClassDefUID() {
      return parentClassDefUID;
   }

   public void setParentClassDefUID(ClassDefinition parentClassDefUID) {
      this.parentClassDefUID = parentClassDefUID;
   }

   public String getDescription() {
      return description;
   }

   public void setDescription(String description) {
      this.description = description;
   }

   public Storage getStorage() {
      return storage;
   }

   public void setStorage(Storage storage) {
      this.storage = storage;
   }

   public List<PropertyDefinition> getAllProperties() {
      List<PropertyDefinition> properties = getPropertyDefs();
      if (parentClassDefUID != null) {
         List<PropertyDefinition> parentProperties = parentClassDefUID.getAllProperties();
         for (PropertyDefinition childProperty : properties){
            PropertyDefinition existed = OCMUtil.getPropertyDefinition(childProperty.getId(), parentProperties);
            if (existed != null){
               parentProperties.remove(existed);
            }
         }
         properties.addAll(parentProperties);
      }
      return properties;
   }

   public String getRealPath() {
      return realPath;
   }

   public void setRealPath(String realPath) {
      this.realPath = realPath;
   }

   public String getCreatedUser() {
      return createdUser;
   }

   public void setCreatedUser(String createdUser) {
      this.createdUser = createdUser;
   }

   public Date getCreatedDated() {
      return createdDated;
   }

   public void setCreatedDated(Date createdDated) {
      this.createdDated = createdDated;
   }

   public String getModifyedUser() {
      return modifyedUser;
   }

   public void setModifyedUser(String modifyedUser) {
      this.modifyedUser = modifyedUser;
   }

   public Date getModifyedDated() {
      return modifyedDated;
   }

   public void setModifyedDated(Date modifyedDated) {
      this.modifyedDated = modifyedDated;
   }
   public Security getSecurity() {
      return security;
   }

   public void setSecurity(Security security) {
      security.setClassDefinition(this);
      this.security = security;
   }

   public ClassDefinition createClassDefInstance() {
      ClassDefinition classInstance = new ClassDefinition();
      switch (this.classDefinitionType) {
         case DOCUMENTCLASSDEF:
            classInstance.setClassDefinitionType(ClassDefinitionType.DOCUMENTCLASSINST);
            break;
         case CUSTOMOBJECTDEF:
            classInstance.setClassDefinitionType(ClassDefinitionType.CUSTOMOBJECTINST);
            break;
         default:
            throw new OCMRuntimeException(ExceptionCode.instance(TypeCodeException.class, TypeCodeException.CLASSDEF));
      }
      String label = this.name + "_INST";
      classInstance.setName("" + DateUtil.getCurrentTime());
      classInstance.setDisplayName(label);
      classInstance.setParentClassDefUID(this);
      classInstance.setStorage(this.storage);
      return classInstance;
   }

   @Override
   public boolean equals(Object obj) {
      ClassDefinition propertyCompare = (ClassDefinition) obj;
      if (this.getId().longValue() == propertyCompare.getId().longValue()) {
         return true;
      }
      return false;
   }
}
